/*
 * Copyright (c) 2010, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/testdriver/RenderTarget2DTestDriver.h"
#include "alkes/testdriver/TestCase.h"
#include "alkes/testdriver/RenderUtility.h"

namespace alkes {


#define SET_RECT(r, new_x, new_y, new_w, new_h) (r).x = new_x; (r).y = new_y; (r).width = new_w; (r).height = new_h

RenderTarget2DTestDriver::RenderTarget2DTestDriver(IRenderTarget2D* target, DecoderFactory* decoder_factory)
: target_(target)
, factory_(decoder_factory)
, enable_grid_(true)
, grid_length_(50)
, enable_image_grid_(false)
{
}

RenderTarget2DTestDriver::~RenderTarget2DTestDriver()
{
}

void RenderTarget2DTestDriver::setEnableGrid(bool is_enable_grid)
{
    enable_grid_ = is_enable_grid;
}

bool RenderTarget2DTestDriver::isEnableGrid() const
{
    return enable_grid_;
}

void RenderTarget2DTestDriver::setGridLength(uint16_t length)
{
    grid_length_ = length < 2 ? 2: length;
}

uint16_t RenderTarget2DTestDriver::getGridLength() const
{
    return grid_length_;
}

void RenderTarget2DTestDriver::renderGrid()
{
    if (!isEnableGrid())
        return ;

    Rect target_rect = target_->getTargetRect();

    Point2D points[2];
    int32_t w, h;

    points[0].y = 0;
    points[1].y = target_rect.height;
    for (w = 0; w < target_rect.width; w += getGridLength())
    {
        points[1].x = points[0].x = (int16_t)w;
        RenderTarget2D_drawLine(target_, points[0], points[1], Color::SILVER, 1);
    }
    points[0].x = 0;
    points[1].x = target_rect.width;
    for (h = 0; h < target_rect.height; h += getGridLength())
    {
        points[1].y = points[0].y = (int16_t)h;
        RenderTarget2D_drawLine(target_, points[0], points[1], Color::SILVER, 1);
    }
}

void RenderTarget2DTestDriver::testSnapshot()
{
}

void RenderTarget2DTestDriver::testDraw2D()
{
    Rect target_rect = target_->getTargetRect();

    AL_TC_ASSERT(!target_rect.isNull());

    target_->beginDraw2D();

    Rect tmp = target_->getTargetRect();
    AL_TC_EQUALS(tmp, target_rect);

    target_->endDraw2D();

    tmp = target_->getTargetRect();
    AL_TC_EQUALS(tmp, target_rect);
}

void RenderTarget2DTestDriver::testScissor()
{
    Rect target_rect = target_->getTargetRect();
    Rect rect;
    Rect tmp;

    SET_RECT(rect, 0,0, 1,1);
    target_->setScissorRect(rect);
    tmp = target_->getScissorRect();
    AL_TC_EQUALS(tmp, rect);

    SET_RECT(rect, 0,0, 64,64);
    target_->setScissorRect(rect);
    tmp = target_->getScissorRect();
    AL_TC_EQUALS(tmp, rect);

    SET_RECT(rect, 0,0, 320,240);
    target_->setScissorRect(rect);
    tmp = target_->getScissorRect();
    AL_TC_EQUALS(tmp, rect);

    SET_RECT(rect, 10,10, 50,100);
    target_->setScissorRect(rect);
    tmp = target_->getScissorRect();
    AL_TC_EQUALS(tmp, rect);

    SET_RECT(rect, 0,0, target_rect.width, target_rect.height);
    target_->setScissorRect(rect);
    tmp = target_->getScissorRect();
    AL_TC_EQUALS(tmp, rect);

    target_->setScissorRect(target_rect);
}

void RenderTarget2DTestDriver::testAlphaBlend()
{
    target_->setState(AL_GS_ALPHA_BLEND, false);
    AL_TC_EQUALS(target_->getState(AL_GS_ALPHA_BLEND), false);
    target_->setState(AL_GS_ALPHA_BLEND, false);
    AL_TC_EQUALS(target_->getState(AL_GS_ALPHA_BLEND), false);

    target_->setState(AL_GS_ALPHA_BLEND, true);
    AL_TC_EQUALS(target_->getState(AL_GS_ALPHA_BLEND), true);
    target_->setState(AL_GS_ALPHA_BLEND, true);
    AL_TC_EQUALS(target_->getState(AL_GS_ALPHA_BLEND), true);

    AlphaBlendMode blends[] = {
        AL_BLEND_ZERO, AL_BLEND_ONE,
        AL_BLEND_SRCCOLOR, AL_BLEND_INVSRCCOLOR,
        AL_BLEND_DSTCOLOR, AL_BLEND_INVDSTCOLOR,
        AL_BLEND_SRCALPHA, AL_BLEND_INVSRCALPHA,
        AL_BLEND_DSTALPHA, AL_BLEND_INVDSTALPHA,
    };
    AlphaBlendMode src_blend, dst_blend;

    int i;

    for (i = 0; i < sizeof(blends)/sizeof(blends[0]); ++i)
    {
        target_->setAlphaBlendMode(blends[i], AL_BLEND_ONE);
        target_->getAlphaBlendMode(&src_blend, &dst_blend);
        AL_TC_EQUALS(src_blend, blends[i]);
        AL_TC_EQUALS(dst_blend, AL_BLEND_ONE);
    }

    for (i = 0; i < sizeof(blends)/sizeof(blends[0]); ++i)
    {
        target_->setAlphaBlendMode(AL_BLEND_ONE, blends[i]);
        target_->getAlphaBlendMode(&src_blend, &dst_blend);
        AL_TC_EQUALS(src_blend, AL_BLEND_ONE);
        AL_TC_EQUALS(dst_blend, blends[i]);
    }

    // keep configuration?
    target_->setAlphaBlendMode(AL_BLEND_ONE, AL_BLEND_ONE);
    target_->setState(AL_GS_ALPHA_BLEND, false);
    AL_TC_EQUALS(target_->getState(AL_GS_ALPHA_BLEND), false);
    target_->getAlphaBlendMode(&src_blend, &dst_blend);
    AL_TC_EQUALS(src_blend, AL_BLEND_ONE);
    AL_TC_EQUALS(dst_blend, AL_BLEND_ONE);
    target_->beginDraw2D();
    AL_TC_EQUALS(target_->getState(AL_GS_ALPHA_BLEND), false);
    target_->getAlphaBlendMode(&src_blend, &dst_blend);
    AL_TC_EQUALS(src_blend, AL_BLEND_ONE);
    AL_TC_EQUALS(dst_blend, AL_BLEND_ONE);
    target_->endDraw2D();
    AL_TC_EQUALS(target_->getState(AL_GS_ALPHA_BLEND), false);
    target_->getAlphaBlendMode(&src_blend, &dst_blend);
    AL_TC_EQUALS(src_blend, AL_BLEND_ONE);
    AL_TC_EQUALS(dst_blend, AL_BLEND_ONE);
}

void RenderTarget2DTestDriver::testBrush()
{
}

void RenderTarget2DTestDriver::testFill()
{
    Rect target_rect = target_->getTargetRect();
    Rect rect;

    // not yet begin draw. Validate not to throw exception.
    AL_TC_EQUALS(target_->fill(Color::RED), true);
    AL_TC_EQUALS(target_->fill(Color::WHITE, &target_rect), true);
    SET_RECT(rect, 1, 1, 4, 4);
    AL_TC_EQUALS(target_->fill(Color::RED, &rect), true);

    Rect::value_type w = target_rect.width/4;
    Rect::value_type h = target_rect.height/4;

    target_->beginDraw2D();

    // out of range. successed but do nothing.
    SET_RECT(rect, -w, -h, w, h);
    AL_TC_EQUALS(target_->fill(Color::PURPLE, &rect), true);
    SET_RECT(rect, target_rect.width, target_rect.height, w, h);
    AL_TC_EQUALS(target_->fill(Color::PURPLE, &rect), true);

    AL_TC_EQUALS(target_->fill(Color::WHITE), true);

    SET_RECT(rect, -w, -h, w*3, h*3);
    AL_TC_EQUALS(target_->fill(Color::RED, &rect), true);

    SET_RECT(rect, 0, 0, w, h);
    AL_TC_EQUALS(target_->fill(Color::BLUE, &rect), true);

    SET_RECT(rect, 0, h, w, h);
    AL_TC_EQUALS(target_->fill(Color::GREEN, &rect), true);

    SET_RECT(rect, w*3, h*2, w, h);
    AL_TC_EQUALS(target_->fill(Color::BLACK, &rect), true);

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testFillRectangle()
{
    Rect target_rect = target_->getTargetRect();
    Rect rect;

    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    target_->beginDraw2D();

    AL_TC_EQUALS(target_->fill(Color::WHITE), true);

    // draw grid
    renderGrid();

    target_->setState(AL_GS_ALPHA_BLEND, false);

    // solid color brush(normal)
    SET_RECT(rect, w, h, w, h);
    AL_TC_EQUALS(RenderTarget2D_fillRectangle(target_, rect, Color::RED), true);

    // solid color brush(enable alpha blend)
    target_->setState(AL_GS_ALPHA_BLEND, true);
    target_->setAlphaBlendMode(AL_BLEND_SRCALPHA, AL_BLEND_INVSRCALPHA);
    SET_RECT(rect, w*2, h, w, h);
    AL_TC_EQUALS(RenderTarget2D_fillRectangle(target_, rect, UColor(0xff, 0, 0, 0x80)), true);

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testDrawRectangle()
{
    Rect target_rect = target_->getTargetRect();
    Rect rect;

    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    target_->beginDraw2D();

    AL_TC_EQUALS(target_->fill(Color::WHITE), true);

    // draw grid
    renderGrid();

    target_->setState(AL_GS_ALPHA_BLEND, false);

    // solid color brush(normal)
    SET_RECT(rect, w, h, w, h);
    AL_TC_EQUALS(RenderTarget2D_drawRectangle(target_, rect, Color::RED, 4), true);

    // solid color brush(enable alpha blend)
    target_->setState(AL_GS_ALPHA_BLEND, true);
    target_->setAlphaBlendMode(AL_BLEND_SRCALPHA, AL_BLEND_INVSRCALPHA);
    SET_RECT(rect, w*2, h, w, h);
    AL_TC_EQUALS(RenderTarget2D_drawRectangle(target_, rect, UColor(0xff, 0, 0, 0x80), 2), true);

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testDrawImage(const char* test_image_path)
{
    ImageProvider provider(factory_);
    Rect target_rect = target_->getTargetRect();
    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    AL_TC_EQUALS(provider.decodeImage(0), 0);
    Image test_image(provider.decodeImage(test_image_path));

    if (!test_image)
    {
        AL_TC_LOG("Cannot open '%s'.", test_image_path);
        return ;
    }

    target_->beginDraw2D();
    AL_TC_EQUALS(target_->fill(Color::WHITE), true);

    // draw grid
    renderGrid();

    if (test_image)
    {
        Point2D dst;
        dst.x = w; dst.y = h;
        RenderTarget2D_drawImage(target_, test_image, test_image.getRect(), dst);
        if (enable_image_grid_)
        {
            URect rect(test_image.getRect());
            rect.x = dst.x;
            rect.y = dst.y;
            RenderTarget2D_drawRectangle(target_, rect, Color::RED, 1);
        }
    }

    if (test_image)
    {
        Point2D dst;
        dst.x = w; dst.y = h*2;
        URect img_rect(test_image.getRect());
        target_->setTextureFilter(AL_TEXFILTER_VALUE_POINT, AL_TEXFILTER_VALUE_POINT);

        // left-side-right
        img_rect.width = -img_rect.width;
        RenderTarget2D_drawImage(target_, test_image, img_rect, dst);
        img_rect.width = -img_rect.width;
        if (enable_image_grid_)
        {
            URect rect(test_image.getRect());
            rect.x = dst.x;
            rect.y = dst.y;
            RenderTarget2D_drawRectangle(target_, rect, Color::RED, 1);
        }

        // up-side-down
        dst.x = w*2;
        img_rect.height = -img_rect.height;
        RenderTarget2D_drawImage(target_, test_image, img_rect, dst);
        if (enable_image_grid_)
        {
            URect rect(test_image.getRect());
            rect.x = dst.x;
            rect.y = dst.y;
            RenderTarget2D_drawRectangle(target_, rect, Color::RED, 1);
        }

        // left-side-right & up-side-down
        dst.x = w*3;
        img_rect.width = -img_rect.width;
        RenderTarget2D_drawImage(target_, test_image, img_rect, dst);
        if (enable_image_grid_)
        {
            URect rect(test_image.getRect());
            rect.x = dst.x;
            rect.y = dst.y;
            RenderTarget2D_drawRectangle(target_, rect, Color::RED, 1);
        }

        URect dst_rect(test_image.getRect());
        dst_rect.x = w;
        dst_rect.y = h*3;
        dst_rect.width = (Rect::value_type)(dst_rect.width * 2.f);
        dst_rect.height = (Rect::value_type)(dst_rect.height * 2.f);

        // clamp
        target_->setTextureAddressMode(AL_TEXADDRESS_MODE_CLAMP, AL_TEXADDRESS_MODE_CLAMP);
        RenderTarget2D_drawImage(target_, test_image, URect(test_image.getRect()), dst_rect);
        if (enable_image_grid_)
            RenderTarget2D_drawRectangle(target_, dst_rect, Color::RED, 1);

        // wrap
        dst_rect.x = w*3;
        target_->setTextureAddressMode(AL_TEXADDRESS_MODE_WRAP, AL_TEXADDRESS_MODE_WRAP);
        RenderTarget2D_drawImage(target_, test_image, URect(test_image.getRect()), dst_rect);
        if (enable_image_grid_)
            RenderTarget2D_drawRectangle(target_, dst_rect, Color::RED, 1);

        // 
        dst_rect.y = h*4;
        dst_rect.x = w;
        target_->setTextureAddressMode(AL_TEXADDRESS_MODE_DISABLE, AL_TEXADDRESS_MODE_WRAP);
        RenderTarget2D_drawImage(target_, test_image, URect(test_image.getRect()), dst_rect);
        if (enable_image_grid_)
            RenderTarget2D_drawRectangle(target_, dst_rect, Color::RED, 1);
    }


    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testDrawLine()
{
    Rect target_rect = target_->getTargetRect();

    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    target_->beginDraw2D();
    target_->setState(AL_GS_ALPHA_BLEND, false);

    target_->fill(Color::WHITE);

    // draw grid
    renderGrid();

    StrokeStyle style;
    Point2D points[2];

    points[0].x = -20; points[0].y = -20;
    points[1].x =  20; points[1].y =  20;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::BLACK, 1, &style);

    points[0].x = -10; points[0].y =  20;
    points[1].x =  20; points[1].y = -10;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::BLACK, 1, &style);

    points[0].x = 40; points[0].y = 40;
    points[1].x = 40; points[1].y = 40;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::BLACK, 1, &style);

    points[0].x = w; points[0].y = h;
    points[1].x = w*2; points[1].y = h*7;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::BLUE, 1, &style);

    points[0].x += w;
    points[1].x += w;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::RED, 2, &style);

    points[0].x += w;
    points[1].x += w;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::GREEN, 3, &style);

    points[0].x += w;
    points[1].x += w;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::GRAY, 6, &style);

    points[0].x += w;
    points[1].x += w;
    RenderTarget2D_drawLine(target_, points[0], points[1], Color::OLIVE, 16, &style);

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testDrawPolyline()
{
    Rect target_rect = target_->getTargetRect();

    Rect::value_type w = (target_rect.width / 16) & 0xfffe;
    Rect::value_type h = (target_rect.height / 16) & 0xfffe;

    target_->beginDraw2D();
    target_->setState(AL_GS_ALPHA_BLEND, false);
    target_->fill(Color::WHITE);

    // draw grid
    renderGrid();

    StrokeStyle style;
    Point2D points[5];

    points[0].x = w  ; points[0].y = h  ;
    points[1].x = w*2; points[1].y = h*2;
    points[2].x = w  ; points[2].y = h*3;
    points[3].x = w*2; points[3].y = h*4;
    points[4].x = w  ; points[4].y = h*5;
    AL_TC_EQUALS(RenderTarget2D_drawPolyline(target_, points, sizeof(points)/sizeof(points[0]), Color::BLACK, 1), true);

    points[0].x = w*2; points[0].y = h  ;
    points[1].x = w*3; points[1].y = h*2;
    points[2].x = w*2; points[2].y = h*3;
    points[3].x = w*3; points[3].y = h*4;
    points[4].x = w*2; points[4].y = h*5;
    AL_TC_EQUALS(RenderTarget2D_drawPolyline(target_, points, sizeof(points)/sizeof(points[0]), Color::RED, 4), true);

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testTransform()
{
    target_->beginDraw2D();

    target_->endDraw2D();
}

#define GUIDE_SIZE  (10)

void RenderTarget2DTestDriver::testTranslate(const char* test_image_path)
{
    ImageProvider provider(factory_);
    Rect target_rect = target_->getTargetRect();
    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    Image test_img(provider.decodeImage(test_image_path));


    target_->beginDraw2D();
    target_->setState(AL_GS_ALPHA_BLEND, false);
    target_->fill(Color::WHITE);

    // draw guide
    RenderTarget2D_drawLine(target_, UPoint2D(w             , h-GUIDE_SIZE), UPoint2D(w             , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w  -GUIDE_SIZE, h           ), UPoint2D(w  +GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2           , h-GUIDE_SIZE), UPoint2D(w*2           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2-GUIDE_SIZE, h           ), UPoint2D(w*2+GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3           , h-GUIDE_SIZE), UPoint2D(w*3           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3-GUIDE_SIZE, h           ), UPoint2D(w*3+GUIDE_SIZE, h           ), Color::BLACK, 1);

    Point2D dst;

    // case 1
    target_->pushMatrix();
    target_->translate(0, 0);
    dst.x = w; dst.y = h;
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), dst);
    target_->popMatrix();

    // case 2
    target_->pushMatrix();
    target_->translate(-8, -8);
    dst.x = w*2; dst.y = h;
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), dst);
    target_->popMatrix();

    // case 3
    target_->pushMatrix();
    target_->translate(8, 8);
    dst.x = w*3; dst.y = h;
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), dst);
    target_->popMatrix();

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testScale(const char* test_image_path)
{
    ImageProvider provider(factory_);
    Rect target_rect = target_->getTargetRect();
    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    Image test_img(provider.decodeImage(test_image_path));

    target_->beginDraw2D();
    target_->setState(AL_GS_ALPHA_BLEND, false);
    target_->fill(Color::WHITE);

    // draw guide
    RenderTarget2D_drawLine(target_, UPoint2D(w             , h-GUIDE_SIZE), UPoint2D(w             , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w  -GUIDE_SIZE, h           ), UPoint2D(w  +GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2           , h-GUIDE_SIZE), UPoint2D(w*2           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2-GUIDE_SIZE, h           ), UPoint2D(w*2+GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3           , h-GUIDE_SIZE), UPoint2D(w*3           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3-GUIDE_SIZE, h           ), UPoint2D(w*3+GUIDE_SIZE, h           ), Color::BLACK, 1);

    Vector2D dst;

    // case 1
    target_->pushMatrix();
    dst.x = w; dst.y = h;
    target_->translate(dst);
    target_->scale(1, 1);
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    // case 2
    target_->pushMatrix();
    dst.x = w*2; dst.y = h;
    target_->translate(dst);
    target_->scale(0.5, 0.5);
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    // case 3
    target_->pushMatrix();
    dst.x = w*3; dst.y = h;
    target_->translate(dst);
    target_->scale(2, 2);
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testRotate(const char* test_image_path)
{
    ImageProvider provider(factory_);
    Rect target_rect = target_->getTargetRect();
    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    Image test_img(provider.decodeImage(test_image_path));

    target_->beginDraw2D();
    target_->setState(AL_GS_ALPHA_BLEND, false);
    target_->fill(Color::WHITE);

    // draw guide
    RenderTarget2D_drawLine(target_, UPoint2D(w             , h-GUIDE_SIZE), UPoint2D(w             , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w  -GUIDE_SIZE, h           ), UPoint2D(w  +GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2           , h-GUIDE_SIZE), UPoint2D(w*2           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2-GUIDE_SIZE, h           ), UPoint2D(w*2+GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3           , h-GUIDE_SIZE), UPoint2D(w*3           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3-GUIDE_SIZE, h           ), UPoint2D(w*3+GUIDE_SIZE, h           ), Color::BLACK, 1);

    Vector2D dst;

    uint16_t iw, ih;
    test_img.getSize(iw, ih);

    // case 1
    target_->pushMatrix();
    dst.x = w; dst.y = h;
    target_->translate(dst);
    target_->rotate(90, UVector2D(iw/2.f, ih/2.f));
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    // case 2
    target_->pushMatrix();
    dst.x = w*2; dst.y = h;
    target_->translate(dst);
    target_->rotate(45, UVector2D(iw/2.f, ih/2.f));
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    // case 3
    target_->pushMatrix();
    dst.x = w*3; dst.y = h;
    target_->translate(dst);
    target_->rotate(170, UVector2D(iw/2.f, ih/2.f));
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    target_->endDraw2D();
}

void RenderTarget2DTestDriver::testSkew(const char* test_image_path)
{
    ImageProvider provider(factory_);
    Rect target_rect = target_->getTargetRect();
    Rect::value_type w = (target_rect.width / 8) & 0xfffe;
    Rect::value_type h = (target_rect.height / 8) & 0xfffe;

    Image test_img(provider.decodeImage(test_image_path));

    target_->beginDraw2D();
    target_->setState(AL_GS_ALPHA_BLEND, false);
    target_->fill(Color::WHITE);

    // draw guide
    RenderTarget2D_drawLine(target_, UPoint2D(w             , h-GUIDE_SIZE), UPoint2D(w             , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w  -GUIDE_SIZE, h           ), UPoint2D(w  +GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2           , h-GUIDE_SIZE), UPoint2D(w*2           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*2-GUIDE_SIZE, h           ), UPoint2D(w*2+GUIDE_SIZE, h           ), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3           , h-GUIDE_SIZE), UPoint2D(w*3           , h+GUIDE_SIZE), Color::BLACK, 1);
    RenderTarget2D_drawLine(target_, UPoint2D(w*3-GUIDE_SIZE, h           ), UPoint2D(w*3+GUIDE_SIZE, h           ), Color::BLACK, 1);

    Vector2D dst;

    // case 1
    target_->pushMatrix();
    dst.x = w; dst.y = h;
    target_->translate(dst);
    target_->skewX(45);
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    // case 2
    target_->pushMatrix();
    dst.x = w*2; dst.y = h;
    target_->translate(dst);
    target_->skewY(45);
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    // case 3
    target_->pushMatrix();
    dst.x = w*3; dst.y = h;
    target_->translate(dst);
    target_->skewX(33);
    target_->skewY(33);
    RenderTarget2D_drawImage(target_, test_img, URect(test_img.getRect()), Point2D::ZERO);
    target_->popMatrix();

    target_->endDraw2D();
}

}
